﻿// -----------------------------------------------------------------------
// <copyright file="HotKeyManager.cs" company="Microsoft">
//   LoLz
// </copyright>
// -----------------------------------------------------------------------

namespace Eeeek
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Threading;

    /// <summary>
    /// Manages the hotkey hooking to stop the program's execution loop
    /// </summary>
    /// <summary>This class allows you to manage a hotkey </summary>
    public class HotKeyManager : IDisposable
    {
        /// <summary>
        /// the ALT key
        /// </summary>
        // ReSharper disable InconsistentNaming
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Reviewed. Suppression is OK here.")]
        public const int MOD_ALT = 1;

        /// <summary>
        /// The control key
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Reviewed. Suppression is OK here.")]
        public const int MOD_CONTROL = 2;

        /// <summary>
        /// The shift key
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Reviewed. Suppression is OK here.")]
        public const int MOD_SHIFT = 4;

        /// <summary>
        /// The windows key
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Reviewed. Suppression is OK here.")]
        public const int MOD_WIN = 8;

        /// <summary>
        /// The hotkey
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Reviewed. Suppression is OK here.")]
        public const int WM_HOTKEY = 0x312;
        // ReSharper restore InconsistentNaming

        /// <summary>
        /// Initializes a new instance of the <see cref="HotKeyManager"/> class.
        /// </summary>
        public HotKeyManager()
        {
            this.Handle = Process.GetCurrentProcess().Handle;
        }

        /// <summary>
        /// Gets the hotkey ID.
        /// </summary>
        public short HotkeyID { get; private set; }

        /// <summary>
        /// Gets or sets the handle.
        /// </summary>
        /// <value>
        /// The handle.
        /// </value>
        private IntPtr Handle { get; set; }

        /// <summary>
        /// Registers the global hot key.
        /// </summary>
        /// <param name="hotkey">The hotkey.</param>
        /// <param name="modifiers">The modifiers.</param>
        /// <param name="handle">The handle.</param>
        public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
        {
            this.UnregisterGlobalHotKey();
            this.Handle = handle;
            this.RegisterGlobalHotKey(hotkey, modifiers);
        }

        /// <summary>
        /// Unregister the hotkey
        /// </summary>
        public void UnregisterGlobalHotKey()
        {
            if (this.HotkeyID != 0)
            {
                NativeMethods.UnregisterHotKey(this.Handle, this.HotkeyID);

                // clean up the atom list
                NativeMethods.GlobalDeleteAtom(this.HotkeyID);
                this.HotkeyID = 0;
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.UnregisterGlobalHotKey();
        }

        /// <summary>
        /// Registers the global hotkey.
        /// </summary>
        /// <param name="hotkey">The hotkey.</param>
        /// <param name="modifiers">The modifiers.</param>
        private void RegisterGlobalHotKey(int hotkey, int modifiers)
        {
            this.UnregisterGlobalHotKey();

            try
            {
                // use the GlobalAddAtom API to get a unique ID (as suggested by MSDN)
                string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + this.GetType().FullName;
                this.HotkeyID = NativeMethods.GlobalAddAtom(atomName);
                if (this.HotkeyID == 0)
                {
                    throw new Exception("Unable to generate unique hotkey ID. Error: " + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture));
                }

                // register the hotkey, throw if any error
                if (!NativeMethods.RegisterHotKey(this.Handle, this.HotkeyID, (uint)modifiers, (uint)hotkey))
                {
                    throw new Exception("Unable to register hotkey. Error: " + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture));
                }
            }
            catch (Exception ex)
            {
                // clean up if hotkey registration failed
                this.Dispose();
                Console.WriteLine(ex);
            }
        }
    }
}